package free6om.research.qart4j;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;

import javax.imageio.ImageIO;

//import org.apache.log4j.PropertyConfigurator;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
import com.google.zxing.common.BitMatrix;

import cn.utils.FileHelper;
import cn.utils.Log;
import joptsimple.OptionParser;
import joptsimple.OptionSet;

/**
 * Hello world!
 */
public class QArt {
    // private static final Logger LOGGER = LoggerFactory.getLogger("test");

    public static void main(String[] args) {
        if (args.length > 0) {
            jarInterface(args);
        } else {
            debugInterface();
        }

    }

    /**
     * 调试入口
     */
    private static void debugInterface() {

        // 输入图片
        String i_input = "sample-input.png";
        // 二维码中图片URL
        String u_url = " http://togetthere.cn/t/d4hge6";
        int v_version = 16;
        int m_mark = 2;
        // 这是一个是否有白边框的参数。二维码外部是否有个边框。没有东西
        int q_quiet = 1;
        int r_rotation = 0;
        // 二维码尺寸
        int z_size = 168;
        // 二维码黑色像素点
        String cb_colorBlack = "FF000000";
        // String cb_colorBlack = "FF66FF00";
        // 二维码白色像素点
        String cw_colorWhite = "EFFFFFFF";
        boolean randControl = false;
        long seed = -1L;
        boolean d_dither = false;
        boolean onlyData = false;
        boolean c_saveControl = false;
        // 距顶尺寸
        int mt_marginTop = 164;
        // 距底尺寸
        int mb_marginBottom = 164;
        // 距左侧尺寸
        int ml_marginLeft = 72;
        // 距右侧尺寸
        int mr_marginRight = 72;
        // 宽度
        int w_width = 324;
        // 高度
        int h_height = 480;
        // 输出文件格式,默认是PNG,可选JPEG
        String f_format = "PNG";
        // 输出的图片。即二维码图片
        String o_output = "output.png";
        // cb cw是数字处理的16进制 Long.parseLong(cb, 16)
        process(i_input, u_url, v_version, m_mark, q_quiet, r_rotation, z_size, Long.parseLong(cb_colorBlack, 16),
                Long.parseLong(cw_colorWhite, 16), randControl, seed, d_dither, onlyData, c_saveControl, mt_marginTop,
                mb_marginBottom, ml_marginLeft, mr_marginRight, w_width, h_height, f_format, o_output);
    }

    /**
     * 入口函数
     * 
     * @param i_filename
     *            输入图片
     * @param u_url
     *            二维码中图片URL
     * @param v_version
     *            官方版本，用于控制大小。 Version-1是21 x 21的矩阵，Version-2是 25 x
     *            25的矩阵，Version-3是29的尺寸，每增加一个version，就会增加4的尺寸，公式是：(V-1)*4 +
     *            21（V是版本号） 最高Version 40，(40-1)*4+21 = 177，所以最高是177 x 177 的正方形
     * @param m_mask
     *            我的理解是掩码，其实也是另类的关键点
     * @param q_quietZone
     *            QR安静区,二维码外部空白区域
     * @param r_rotation
     *            图像旋转，顺时针旋转，取值范围0到3之前
     * @param z_size
     *            二维码尺寸
     * @param cb_colorBlack
     *            二维码黑色像素点
     * @param cw_colorWhite
     *            二维码白色像素点
     * @param randControl
     *            是否控制(调整)
     * @param seed
     *            ??二维码可以填充时间
     * @param d_dither
     *            图片抖动？图片处理
     * @param onlyData_onlyDataBits
     *            ?? 只根据数据位来模拟图像.应该还有其他处理。待验证
     * @param c_saveControl
     *            控制像素展示相关
     * @param mt_marginTop
     *            距顶尺寸
     * @param mb_marginBottom
     *            距底尺寸
     * @param ml_marginLeft
     *            距左侧尺寸
     * @param mr_marginRight
     *            距右侧尺寸
     * @param w_width
     *            宽度
     * @param h_height
     *            高度
     * @param f_outputFormat
     *            输出文件格式,默认是PNG,可选JPEG
     * @param o_output
     *            输出的图片。即二维码图片
     */
    private static void process(String i_filename, String u_url, int v_version, int m_mask, int q_quietZone,
            int r_rotation, int z_size, long cb_colorBlack, long cw_colorWhite, boolean randControl, long seed,
            boolean d_dither, boolean onlyData_onlyDataBits, boolean c_saveControl, Integer mt_marginTop,
            Integer mb_marginBottom, Integer ml_marginLeft, Integer mr_marginRight, int w_width, int h_height,
            String f_outputFormat, String o_output) {
        if (seed == -1) {
            seed = System.currentTimeMillis();
        }

        // 参数合法性检查
        try {
            /**
             * 1.文件加载到内存
             */
            BufferedImage input = ImageUtil.loadImage(i_filename, w_width, h_height);

            /**
             * 2.计算二维码的尺寸。
             */
            // 个人理解是官方版本，算法和(V-1)*4 +21类似效果。
            int qrSizeWithoutQuiet = 17 + 4 * v_version;
            int qrSize = qrSizeWithoutQuiet + q_quietZone * 2;
            if (z_size < qrSize) { // 不缩放
                z_size = qrSize;
            }
            int scale = z_size / qrSize;
            int targetQrSizeWithoutQuiet = qrSizeWithoutQuiet * scale;

            /**
             * 3.生成二维码的位置
             */
            Rectangle inputImageRect = new Rectangle(new Point(0, 0), w_width, h_height);
            int startX = 0, startY = 0;
            if (ml_marginLeft != null) {
                startX = ml_marginLeft;
            } else if (mr_marginRight != null) {
                startX = w_width - mr_marginRight - z_size;
            }
            if (mt_marginTop != null) {
                startY = mt_marginTop;
            } else if (mb_marginBottom != null) {
                startY = h_height - mb_marginBottom - z_size;
            }

            Rectangle qrRect = new Rectangle(new Point(startX, startY), z_size, z_size);
            Rectangle qrWithoutQuietRect = new Rectangle(
                    new Point(startX + (z_size - targetQrSizeWithoutQuiet) / 2,
                            startY + (z_size - targetQrSizeWithoutQuiet) / 2),
                    targetQrSizeWithoutQuiet, targetQrSizeWithoutQuiet);

            int[][] target = null;
            int dx = 0, dy = 0;
            BufferedImage targetImage = null;
            Rectangle targetRect = inputImageRect.intersect(qrWithoutQuietRect);
            if (targetRect == null) {
                Log.w("no intersect zone");
                target = new int[0][0];
            } else {
                targetImage = input.getSubimage(targetRect.start.x, targetRect.start.y, targetRect.width,
                        targetRect.height);
                int scaledWidth = targetRect.width / scale;
                int scaledHeight = targetRect.height / scale;
                BufferedImage scaledImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);
                Graphics graphics = scaledImage.createGraphics();
                graphics.drawImage(targetImage, 0, 0, scaledWidth, scaledHeight, null);
                graphics.dispose();

                target = ImageUtil.makeTarget(scaledImage, 0, 0, scaledWidth, scaledHeight);
                dx = (qrWithoutQuietRect.start.x - targetRect.start.x) / scale;
                dy = (qrWithoutQuietRect.start.y - targetRect.start.y) / scale;
            }

            /**
             * 4.二维码生成
             */
            Image image = new Image(target, dx, dy, u_url, v_version, m_mask, r_rotation, randControl, seed, d_dither,
                    onlyData_onlyDataBits, c_saveControl);
            QRCode qrCode = image.encode();
            // 应该是生成像素单位的位图
            BitMatrix bitMatrix = ImageUtil.makeBitMatrix(qrCode, q_quietZone, z_size);
            MatrixToImageConfig config = new MatrixToImageConfig(cb_colorBlack, cw_colorWhite);
            BufferedImage finalQrImage = MatrixToImageWriter.toBufferedImage(bitMatrix, config);

            /**
             * 5.这部分覆盖图片到一起
             */
            Rectangle finalRect = qrRect.union(inputImageRect);
            BufferedImage finalImage = new BufferedImage(finalRect.width, finalRect.height, BufferedImage.TYPE_INT_RGB);
            Graphics graphics = finalImage.createGraphics();

            graphics.drawImage(input, inputImageRect.start.x - finalRect.start.x,
                    inputImageRect.start.y - finalRect.start.y, inputImageRect.width, inputImageRect.height, null);
            graphics.drawImage(finalQrImage, qrRect.start.x - finalRect.start.x, qrRect.start.y - finalRect.start.y,
                    qrRect.width, qrRect.height, null);
            graphics.dispose();

            /**
             * 6.数据格式兼容。兼容JPEG格式
             */
            if (f_outputFormat.toUpperCase().contentEquals("JPEG")) {
                // Creating a non Alpha channel bufferedImage so that alpha
                // channel does not corrupt jpeg.
                BufferedImage nonAlpha = new BufferedImage(finalImage.getWidth(), finalImage.getHeight(),
                        BufferedImage.TYPE_INT_RGB);
                Graphics nonAlphaGraphics = nonAlpha.createGraphics();

                nonAlphaGraphics.setColor(Color.white);
                nonAlphaGraphics.fillRect(0, 0, finalImage.getWidth(), finalImage.getHeight());
                nonAlphaGraphics.drawImage(finalImage, 0, 0, null);
                nonAlphaGraphics.dispose();

                finalImage = nonAlpha;
            }

            /**
             * 7.图片保存
             */
            String fileName = FileHelper.edit(o_output);
            ImageIO.write(finalImage, f_outputFormat, new File(fileName));

        } catch (Exception e) {
            // LOGGER.error("encode error", e);
            Log.e("encode error", e);
        }
    }

    private static void jarInterface(String[] args) {

        // JOpt Simple。 这个开元检查库 地址
        // http://pholser.github.io/jopt-simple/index.html
        // joptsimple.OptionParser 这是一个命令行的解析器，一般用于检查参数等
        OptionParser parser = new OptionParser() {
            {
                acceptsAll(Arrays.asList("help", "?"), "show this help.");
                acceptsAll(Arrays.asList("l", "log4j")).withRequiredArg().ofType(String.class)
                        .describedAs("log config file path.").defaultsTo("./src/main/config/log4j.properties");
                // 输入图片
                // input image
                acceptsAll(Arrays.asList("i", "input")).withRequiredArg().ofType(String.class)
                        .describedAs("input image file").defaultsTo("input.png");
                // 二维码中嵌套的URL
                acceptsAll(Arrays.asList("u", "url")).withRequiredArg().ofType(String.class)
                        .describedAs("URL to encode").defaultsTo("http://free6om.me");
                /**
                 * 生成二维码的相应参数 https://coolshell.cn/articles/10590.html
                 */
                // output QR code
                // 官方版本，用于控制大小。 Version-1是21 x 21的矩阵，Version-2是 25 x
                // 25的矩阵，Version-3是29的尺寸，每增加一个version，就会增加4的尺寸，
                // 公式是：(V-1)*4 + 21（V是版本号）
                // 最高Version 40，(40-1)*4+21 = 177，所以最高是177 x 177 的正方形
                acceptsAll(Arrays.asList("v", "version")).withRequiredArg().ofType(Integer.class)
                        .describedAs("QR version: 1 - 40").defaultsTo(6);
                // 我的理解是掩码，其实也是另类的关键点
                acceptsAll(Arrays.asList("m", "mask")).withRequiredArg().ofType(Integer.class)
                        .describedAs("QR mask: 0 - 7").defaultsTo(2);
                // 二维码外部空白区域
                acceptsAll(Arrays.asList("q", "quiet")).withRequiredArg().ofType(Integer.class)
                        .describedAs("QR quiet zone").defaultsTo(2);
                // 图像旋转，顺时针旋转，取值范围0到3之前
                acceptsAll(Arrays.asList("r", "rotation")).withRequiredArg().ofType(Integer.class)
                        .describedAs("rotation of the image in clockwise: 0 - 3").defaultsTo(0);
                // 二维码尺寸
                acceptsAll(Arrays.asList("z", "size")).withRequiredArg().ofType(Integer.class)
                        .describedAs("output QR code size, 0 means don't scale").defaultsTo(0);
                // 二维码黑色像素点
                acceptsAll(Arrays.asList("cb", "colorBlack")).withOptionalArg().ofType(String.class)
                        .describedAs("ARGB of the black pixel of the QR code, 0x00000000 - 0xFFFFFFFF")
                        .defaultsTo("FF000000");
                // 二维码白色像素点
                acceptsAll(Arrays.asList("cw", "colorWhite")).withOptionalArg().ofType(String.class)
                        .describedAs("ARGB of the white pixel of the QR code, 0x00000000 - 0xFFFFFFFF")
                        .defaultsTo("FFFFFFFF");
                /**
                 * 二维码生成方式
                 */
                // how to generate QR code
                // 是否控制(调整)
                acceptsAll(Arrays.asList("randControl")).withRequiredArg().ofType(Boolean.class)
                        .describedAs("rand control or not").defaultsTo(Boolean.FALSE);
                // ??二维码可以填充时间
                acceptsAll(Arrays.asList("seed")).withRequiredArg().ofType(Long.class)
                        .describedAs("random seed, -1 use System.currentMillis()").defaultsTo(-1L);
                // 图片抖动？图片处理
                acceptsAll(Arrays.asList("d", "dither")).withRequiredArg().ofType(Boolean.class)
                        .describedAs("dither image or not").defaultsTo(Boolean.FALSE);
                // ?? 只根据数据位来模拟图像.应该还有其他处理。待验证
                acceptsAll(Arrays.asList("onlyData")).withRequiredArg().ofType(Boolean.class)
                        .describedAs("only use data bits to emulate input image").defaultsTo(Boolean.FALSE);
                // 控制像素展示相关
                acceptsAll(Arrays.asList("c", "saveControl")).withRequiredArg().ofType(Boolean.class)
                        .describedAs("show pixel we have control").defaultsTo(Boolean.FALSE);
                /**
                 * 输出图片相关设置
                 */
                // output image
                // 距顶尺寸
                acceptsAll(Arrays.asList("mt", "marginTop")).withOptionalArg().ofType(Integer.class)
                        .describedAs("margin top").defaultsTo(0);
                // 距底尺寸
                acceptsAll(Arrays.asList("mb", "marginBottom")).withOptionalArg().ofType(Integer.class)
                        .describedAs("margin bottom");
                // 距左侧尺寸
                acceptsAll(Arrays.asList("ml", "marginLeft")).withOptionalArg().ofType(Integer.class)
                        .describedAs("margin left").defaultsTo(0);
                // 距右侧尺寸
                acceptsAll(Arrays.asList("mr", "marginRight")).withOptionalArg().ofType(Integer.class)
                        .describedAs("margin right");
                // 宽度
                acceptsAll(Arrays.asList("w", "width")).withRequiredArg().ofType(Integer.class)
                        .describedAs("output image width, 0 means same as input image width").defaultsTo(180);
                // 高度
                acceptsAll(Arrays.asList("h", "height")).withRequiredArg().ofType(Integer.class)
                        .describedAs("output image height, 0 means same as input image height").defaultsTo(180);
                // 输出文件格式,默认是PNG,可选jpg
                acceptsAll(Arrays.asList("f", "format")).withRequiredArg().ofType(String.class)
                        .describedAs("output image format").defaultsTo("PNG");
                // 输出的图片。即二维码图片
                acceptsAll(Arrays.asList("o", "output")).withRequiredArg().ofType(String.class)
                        .describedAs("output image file").defaultsTo("output.png");

            }
        };
        OptionSet options = parser.parse(args);
        if (options.hasArgument("help") || options.has("?") || args.length == 0) {
            try {
                parser.printHelpOn(System.out);
            } catch (IOException e) {
            }
            return;
        }

        /**
         * 获取所有参数
         */
        // String log4j = (String) options.valueOf("l");
        // input
        String filename = (String) options.valueOf("i");
        String url = (String) options.valueOf("u");

        // QR code
        int version = (Integer) options.valueOf("v");
        int mask = (Integer) options.valueOf("m");
        int quietZone = (Integer) options.valueOf("q");
        int rotation = (Integer) options.valueOf("r");
        int size = (Integer) options.valueOf("z");
        int colorBlack = (int) Long.parseLong((String) options.valueOf("cb"), 16);
        int colorWhite = (int) Long.parseLong((String) options.valueOf("cw"), 16);

        // how to generate QR code
        boolean randControl = (Boolean) options.valueOf("randControl");
        long seed = (Long) options.valueOf("seed");
        if (seed == -1) {
            seed = System.currentTimeMillis();
        }
        boolean dither = (Boolean) options.valueOf("d");
        boolean onlyDataBits = (Boolean) options.valueOf("onlyData");
        boolean saveControl = (Boolean) options.valueOf("saveControl");

        // output image
        int width = (Integer) options.valueOf("w");
        int height = (Integer) options.valueOf("h");

        Integer marginTop = options.has("mt") ? (Integer) options.valueOf("mt") : null;
        Integer marginBottom = options.has("mb") ? (Integer) options.valueOf("mb") : null;
        Integer marginLeft = options.has("ml") ? (Integer) options.valueOf("ml") : null;
        Integer marginRight = options.has("mr") ? (Integer) options.valueOf("mr") : null;

        String outputFormat = (String) options.valueOf("f");
        String output = (String) options.valueOf("o");

        // log4j config
        // configLog(log4j);
        // todo validate input params, make sure all of them are valid

        try {
            BufferedImage input = ImageUtil.loadImage(filename, width, height);

            int qrSizeWithoutQuiet = 17 + 4 * version;
            int qrSize = qrSizeWithoutQuiet + quietZone * 2;
            if (size < qrSize) { // don't scale
                size = qrSize;
            }
            int scale = size / qrSize;
            int targetQrSizeWithoutQuiet = qrSizeWithoutQuiet * scale;

            Rectangle inputImageRect = new Rectangle(new Point(0, 0), width, height);
            int startX = 0, startY = 0;
            if (marginLeft != null) {
                startX = marginLeft;
            } else if (marginRight != null) {
                startX = width - marginRight - size;
            }
            if (marginTop != null) {
                startY = marginTop;
            } else if (marginBottom != null) {
                startY = height - marginBottom - size;
            }

            Rectangle qrRect = new Rectangle(new Point(startX, startY), size, size);
            Rectangle qrWithoutQuietRect = new Rectangle(
                    new Point(startX + (size - targetQrSizeWithoutQuiet) / 2,
                            startY + (size - targetQrSizeWithoutQuiet) / 2),
                    targetQrSizeWithoutQuiet, targetQrSizeWithoutQuiet);

            int[][] target = null;
            int dx = 0, dy = 0;
            BufferedImage targetImage = null;
            Rectangle targetRect = inputImageRect.intersect(qrWithoutQuietRect);
            if (targetRect == null) {
                // LOGGER.warn("no intersect zone");
                Log.w("no intersect zone");
                target = new int[0][0];
            } else {
                targetImage = input.getSubimage(targetRect.start.x, targetRect.start.y, targetRect.width,
                        targetRect.height);
                int scaledWidth = targetRect.width / scale;
                int scaledHeight = targetRect.height / scale;
                BufferedImage scaledImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);
                Graphics graphics = scaledImage.createGraphics();
                graphics.drawImage(targetImage, 0, 0, scaledWidth, scaledHeight, null);
                graphics.dispose();

                target = ImageUtil.makeTarget(scaledImage, 0, 0, scaledWidth, scaledHeight);
                dx = (qrWithoutQuietRect.start.x - targetRect.start.x) / scale;
                dy = (qrWithoutQuietRect.start.y - targetRect.start.y) / scale;
            }

            Image image = new Image(target, dx, dy, url, version, mask, rotation, randControl, seed, dither,
                    onlyDataBits, saveControl);

            QRCode qrCode = image.encode();
            BitMatrix bitMatrix = ImageUtil.makeBitMatrix(qrCode, quietZone, size);

            MatrixToImageConfig config = new MatrixToImageConfig(colorBlack, colorWhite);
            BufferedImage finalQrImage = MatrixToImageWriter.toBufferedImage(bitMatrix, config);

            Rectangle finalRect = qrRect.union(inputImageRect);
            BufferedImage finalImage = new BufferedImage(finalRect.width, finalRect.height, BufferedImage.TYPE_INT_RGB);
            Graphics graphics = finalImage.createGraphics();

            graphics.drawImage(input, inputImageRect.start.x - finalRect.start.x,
                    inputImageRect.start.y - finalRect.start.y, inputImageRect.width, inputImageRect.height, null);
            graphics.drawImage(finalQrImage, qrRect.start.x - finalRect.start.x, qrRect.start.y - finalRect.start.y,
                    qrRect.width, qrRect.height, null);
            graphics.dispose();

            /**
             * 兼容JPG格式
             */
            if (outputFormat.toUpperCase().contentEquals("JPEG")) {
                // Creating a non Alpha channel bufferedImage so that alpha
                // channel does not corrupt jpeg.
                BufferedImage nonAlpha = new BufferedImage(finalImage.getWidth(), finalImage.getHeight(),
                        BufferedImage.TYPE_INT_RGB);
                Graphics nonAlphaGraphics = nonAlpha.createGraphics();

                nonAlphaGraphics.setColor(Color.white);
                nonAlphaGraphics.fillRect(0, 0, finalImage.getWidth(), finalImage.getHeight());
                nonAlphaGraphics.drawImage(finalImage, 0, 0, null);
                nonAlphaGraphics.dispose();

                finalImage = nonAlpha;
            }

            ImageIO.write(finalImage, outputFormat, new File(output));

        } catch (Exception e) {
            // LOGGER.error("encode error", e);
            Log.e("encode error", e);
        }
    }

    /**
     * log配置. 简单逻辑:本地与文件则退出，没文件则创建文件。
     * 
     * @param configFileName
     */
    // private static void configLog(String configFileName) {
    // if (new File(configFileName).exists()) {
    // PropertyConfigurator.configure(configFileName);
    // return;
    // }
    //
    // Properties properties = new Properties();
    //
    // properties.setProperty("log4j.rootLogger", "DEBUG, CA");
    // properties.setProperty("log4j.appender.CA",
    // "org.apache.log4j.ConsoleAppender");
    // properties.setProperty("log4j.appender.CA.layout",
    // "org.apache.log4j.PatternLayout");
    // properties.setProperty("log4j.appender.CA.layout.ConversionPattern",
    // "%d{yyyy-MM-dd HH:mm:ss.SSS} %-4r [%t] %-5p %c %x - %m%n");
    // PropertyConfigurator.configure(properties);
    // }
}
